package cn.com.cetccst.tools.pm;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.graphics.drawable.Drawable;
import android.net.Uri;

import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import androidx.fragment.app.FragmentActivity;

import com.permissionx.guolindev.PermissionX;
import com.permissionx.guolindev.callback.RequestCallback;

import java.io.File;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import cn.com.cetccst.tools.R;
import cn.com.cetccst.tools.log.CommonLogger;

public class AppPackage {
    public static final int APP_NOT_INSTALL_VERSION_CODE = -1;
    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    public static final String URI_FILE_PREFIX = "file://";
    ;

    public static void installApk(Context context, String apkPath) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        String type = "application/vnd.android.package-archive";
        Uri uri;
        uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileProvider", new File(apkPath));
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.setDataAndType(uri, type);
        context.startActivity(intent);
    }

    public static void uninstallApk(Context context, String appPackageName) {
        Uri uri = Uri.parse("package:" + appPackageName);
        Intent intent = new Intent(Intent.ACTION_DELETE, uri);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

    public interface SignatureAlgorithm {
        String ALGORITHM_MD5 = "MD5";
        String ALGORITHM_SHA1 = "SHA";
        String ALGORITHM_SHA256 = "SHA256";
    }

    public static String getSignatureHash(Context context, String packageName, String algorithm) {
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, android.content.pm.PackageManager.GET_SIGNATURES);
            Signature[] signatures = packageInfo.signatures;
            return getSignatureHash(signatures, algorithm);
        } catch (android.content.pm.PackageManager.NameNotFoundException e) {
            CommonLogger.error("get signature error", e);
        }
        return "";
    }

    public static boolean isWebApp(String appId) {
        return appId.toLowerCase().startsWith("http");
    }

    public static boolean isPackageFound(Context context, String packageName) {
        PackageInfo packageInfo;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(packageName,
                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
            return packageInfo != null;
        } catch (PackageManager.NameNotFoundException e) {
            CommonLogger.error("Could not get package info for " + packageName, e);
        }
        return false;
    }

    public static int getVersionCode(Context context, String packageName) {
        PackageInfo packageInfo = null;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES);
        } catch (PackageManager.NameNotFoundException e) {
            CommonLogger.error("not found package " + packageName, e);
        }
        if (packageInfo != null) {
            return packageInfo.versionCode;
        }
        return APP_NOT_INSTALL_VERSION_CODE;
    }

    public static String getSSVersionName(Context context) {
        return getVersionName(context, context.getPackageName());
    }

    public static String getVersionName(Context context, String packageName) {
        PackageInfo packageInfo = null;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
        } catch (PackageManager.NameNotFoundException e) {
            CommonLogger.error("not found package " + packageName, e);
        }
        if (packageInfo != null) {
            return packageInfo.versionName;
        }
        return "";
    }

    public static String getPackageNameFromApk(Context context, String apkPath) {
        String path = apkPath.replace(URI_FILE_PREFIX, "");
        PackageInfo packageInfo = context.getPackageManager().getPackageArchiveInfo(path, 0);
        return packageInfo != null ? packageInfo.packageName : null;
    }

    public static void launchAppByPackageName(@NonNull Context context, String packageName) {
        Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
        if (intent != null) {
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            context.startActivity(intent);
        }
    }

    public static void requestPermission(@NonNull FragmentActivity activity, String description, RequestCallback callback, String... permissions) {
        PermissionX.init(activity)
                //要申请的权限列表（可直接传入上方的requestList）
                .permissions(permissions)
                //（可选）修改默认对话框的颜色（浅色主题、深色主题）（默认已适配深色模式）
                .setDialogTintColor(activity.getColor(R.color.permission_theme_color), activity.getColor(R.color.permission_theme_dark_color))
                //（可选）在申请前先弹窗说明（下方通过beforeRequest区分）
                .explainReasonBeforeRequest()
                //弹窗申请原因（提供功能函数的上下文对象，被拒绝或者还未申请的权限清单，是否是申请前）
                .onExplainRequestReason((scope, deniedList) -> scope.showRequestReasonDialog(
                        deniedList, description, activity.getString(R.string.grant), activity.getString(R.string.deny)))
                //针对被拒绝且不再询问的权限进行解释并跳转到系统设置
                .onForwardToSettings((scope, deniedList) -> scope.showForwardToSettingsDialog(deniedList, activity.getString(R.string.setting_manual), activity.getString(R.string.setting_manual_positive)))
                .request(callback);
    }

    public static Drawable getDrawable(Context context, String packageName) {
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
            return packageInfo.applicationInfo.loadIcon(context.getPackageManager());
        } catch (PackageManager.NameNotFoundException e) {
            CommonLogger.error("Could not get icon info for " + packageName, e);
        }
        return null;
    }

    public static long getPackageSize(Context context, String packageName) {
        long size = 0;
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
            if (packageInfo != null) {
                ApplicationInfo applicationInfo = packageInfo.applicationInfo;
                if (applicationInfo != null) {
                    String path = applicationInfo.publicSourceDir != null ? applicationInfo.publicSourceDir : applicationInfo.sourceDir;
                    if (path != null) {
                        File file = new File(path);
                        if (file.exists()) {
                            size = file.length();
                        }
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            CommonLogger.error("Could not get icon info for " + packageName, e);
        }
        return size;
    }

    public static boolean isSystemPackage(Context context, String packageName) {
        try {
            int flags = Objects.requireNonNull(context.getPackageManager().getPackageInfo(packageName, 0).applicationInfo).flags;
            return ((flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo.FLAG_SYSTEM) || ((flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
        } catch (PackageManager.NameNotFoundException e) {
            CommonLogger.error("Could not get system package info for " + packageName, e);
            return false;
        }
    }

    public static List<ApplicationInfo> getInstalledApps(Context context) {
        List<ApplicationInfo> appInfos = new ArrayList<>();
        Set<String> appInfoSet = new HashSet<>();
        Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
        List<ResolveInfo> activities =
                context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_ALL);
        for (ResolveInfo resolveInfo : activities) {
            try {
                String packageName = resolveInfo.activityInfo.applicationInfo.packageName;
                if (appInfoSet.contains(packageName)){
                    continue;
                }
                appInfoSet.add(packageName);
                appInfos.add(resolveInfo.activityInfo.applicationInfo);
            } catch (Exception e) {
                CommonLogger.error("get all app info error ", e);
            }
        }
        return appInfos;
    }
    //---------private
    private static String getSignatureHash(Signature[] signatures, String algorithm) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            if (signatures != null) {
                for (Signature s : signatures)
                    digest.update(s.toByteArray());
            }
            return toHexString(digest.digest());
        } catch (Exception e) {
            CommonLogger.error("get signature hash error", e);
        }
        return null;
    }

    private static String toHexString(byte[] b) {
        StringBuilder sb = new StringBuilder(b.length * 2);
        for (int i = 0; i < b.length; i++) {
            sb.append(HEX_DIGITS[(b[i] & 0xf0) >>> 4]);
            sb.append(HEX_DIGITS[b[i] & 0x0f]);
            if (i < b.length - 1) {
                sb.append(":");
            }
        }
        return sb.toString();
    }
}
